package com.amazonaws.services.dynamodbv2.json.demo.mars.worker;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.amazonaws.services.dynamodbv2.json.demo.mars.util.JSONParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
/**
* Retrieves a mission manifest and processes it into a map of sol number to sol URL.
*/
public class DynamoDBMissionWorker implements Callable<Map<Integer, String>> {
/**
* Logger for DynamoDBMissionWorker.
*/
private static final Logger LOGGER = Logger.getLogger(DynamoDBMissionWorker.class.getName());
// Parsing constants
/**
* JSON key for resource type.
*/
private static final String RESOURCE_TYPE_KEY = "type";
/**
* Supported resource types. Used to fail gracefully if NASA changes data model.
*/
private static final List<String> SUPPORTED_TYPES = Arrays.asList("mer-images-manifest-1.0",
"msl-images-manifest-2.0");
/**
* JSON key for sols array.
*/
private static final String SOLS_LIST_KEY = "sols";
/**
* JSON key for sol number.
*/
private static final String SOL_ID_KEY = "sol";
/**
* JSON key for sol URL.
*/
private static final String SOL_URL_KEY = "url";
/**
* Retrieves and parses a mission manifest to a map of sol numbers to sol URLs.
*
* @param url
* Location of the mission manifest
* @param connectTimeout
* Timeout for retrieving the mission manifest
* @return Map of sol number to sol URL contained in the mission manifest
* @throws IOException
* Invalid URL, invalid JSON data, or connection error
*/
public static Map<Integer, String> getSolJSON(final URL url, final int connectTimeout) throws IOException {
final Map<Integer, String> map = new HashMap<Integer, String>();
// Retrieve the JSON data
final JsonNode manifest = JSONParser.getJSONFromURL(url, connectTimeout);
// Validate the JSON data version
if (!manifest.has(RESOURCE_TYPE_KEY) || !SUPPORTED_TYPES.contains(manifest.get(RESOURCE_TYPE_KEY).asText())) {
throw new IllegalArgumentException("Manifest version verification failed");
}
// Validate that the JSON data contains a sol list
if (!manifest.has(SOLS_LIST_KEY)) {
throw new IllegalArgumentException("Manifest does not contain a sol list");
}
final ArrayNode sols = (ArrayNode) manifest.get(SOLS_LIST_KEY);
// Process each sol in the sol list
for (int i = 0; i < sols.size(); i++) {
final JsonNode sol = sols.path(i);
if (sol.has(SOL_ID_KEY) && sol.has(SOL_URL_KEY)) {
final Integer solID = sol.get(SOL_ID_KEY).asInt();
final String solURL = sol.get(SOL_URL_KEY).asText();
if (solID != null && solURL != null) {
// Add valid sol to the map
map.put(solID, solURL);
} else {
LOGGER.warning("Sol contains unexpected values: " + sol);
}
} else {
LOGGER.warning("Sol missing required keys: ");
}
}
return map;
}
// State
/**
* URL of the mission manifest.
*/
private final String manifestURL;
/**
* Timeout for retreiving mission manifest.
*/
private final int connectTimeout;
/**
* Constructs new {@link DynamoDBMissionWorker} to retrieve manifest at the specified URL. Will use specified
* timeout when connecting.
*
* @param manifestURL
* URL of the mission manifest to retrieve
* @param connectTimeout
* Amount of time in milliseconds to timeout while retrieving manifest
*/
public DynamoDBMissionWorker(final String manifestURL, final int connectTimeout) {
this.manifestURL = manifestURL;
this.connectTimeout = connectTimeout;
}
/**
* {@inheritDoc}
*/
@Override
public Map<Integer, String> call() throws Exception {
try {
// Always check manifest - sol could have updated
final Map<Integer, String> sols = getSolJSON(new URL(manifestURL), connectTimeout);
LOGGER.info("Processed Manifest (" + sols.size() + " sols): " + manifestURL);
return sols;
} catch (final Exception e) {
LOGGER.log(Level.WARNING, "Skipping manifest: " + manifestURL, e);
return Collections.emptyMap();
}
}
}